<?php

namespace app\common\library;

use think\facade\Cache;
use think\facade\Log;

class RedissLock
{
    const BLOCK = 1; //阻塞
    const UNBLOCK = 0; //非阻塞
    const MIN_EXPIRE = 2000;
    protected $expire = 50000;
    protected $lockKey;
    protected $keyExpireTime;
    protected $type;
    protected $spaceTime = 1000000;
    protected $redis;
    private $isGetLock = false;
    private $expireSuccess = false;
    static $instance;

    private function init_lock_params($name, $type = self::BLOCK, $expire = null, $spaceTime = null)
    {
        $this->lockKey = __CLASS__ . ':' . md5($name);
        if ($type === self::UNBLOCK) {
            $this->type = $type;
        } else {
            $this->type = self::BLOCK;
        }
        $this->setExpire($expire);
        if (!empty($spaceTime) && $spaceTime > 0) {
            $this->spaceTime = $spaceTime * 1000;
        }
        $this->redis = Cache::store('redis');
    }

    public function getLockKey()
    {
        return $this->lockKey;
    }

    private function setExpire($expire = null)
    {
        if (!empty($expire) && $expire >= self::MIN_EXPIRE) {
            $this->expire = $expire;
        }
    }

    public static function getLockExpire($name)
    {
        return Cache::store('redis')->pttl(__CLASS__ . ':' . md5($name));
    }

    public static function getInstance()
    {
        if (!(self::$instance instanceof self)) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public static function lock($name, $type = self::BLOCK, $expire = null, $spaceTime = null)
    {
        $lock = self::getInstance();
        $lock->init_lock_params($name, $type, $expire, $spaceTime);
        return $lock->execLock();
    }

    private function execLock()
    {
        while (true) {
            $this->createExpireTime();
            if ($this->redis->setnx($this->lockKey, $this->keyExpireTime)) {
                return $this->getLock();
            } else {
                $pttl = $this->redis->pttl($this->lockKey);
                switch ($pttl) {
                    case -2:
                        break;
                    case -1:
                        $old1 = $this->redis->get($this->lockKey);
                        if ($old1 === false) {
                            break;
                        } else if (self::getMilliTime() > $old1) {
                            $old2 = $this->redis->getSet($this->lockKey, $this->keyExpireTime);
                            if ($old1 === $old2) {
                                return $this->getLock();
                            }
                        }
                    default:
                        if ($this->type === self::UNBLOCK) {
                            return false;
                        } else {
                            usleep($this->spaceTime);
                        }
                }
            }
        }
    }

    public function delayLockTime($millisecond = null)
    {
        if ($this->safeJudge()) {
            $this->setExpire($millisecond);
            $this->createExpireTime();
            $this->redis->set($this->lockKey, $this->keyExpireTime);
            return (boolean)$this->getLock();
        } else {
            return false;
        }
    }

    public function safeJudge($threshold = 5)
    {
        $threshold = $threshold > 5 ? $threshold : 5;
        if ($this->expireSuccess) {
            if ($this->isGetLock && $this->redis->pttl($this->lockKey) > $threshold && isset($this->keyExpireTime) && $this->redis->get($this->lockKey) - $this->keyExpireTime < 10 && $this->keyExpireTime - self::getMilliTime() > $threshold) {
                return true;
            }
        } else {
            if ($this->isGetLock && isset($this->keyExpireTime) && $this->redis->get($this->lockKey) - $this->keyExpireTime < 10 && $this->keyExpireTime - self::getMilliTime() > $threshold) {
                return true;
            }
        }
        return false;
    }

    /*判断锁是否还存在
     *return boolean
     */
    public function lockExist()
    {
        if ($this->safeJudge()) {
            return true;
        } else {
            $this->isGetLock = false;
            return false;
        }
    }

    //释放锁
    public function unlock()
    {
        if ($this->safeJudge()) {
            $this->isGetLock = false;
            return $this->redis->del($this->lockKey);
        }
        $this->isGetLock = false;
    }

    private function getLock()
    {
        $this->isGetLock = true;
        $this->expireSuccess = $this->redis->pExpire($this->lockKey, $this->expire);
        return $this; //获得锁
    }

    private function createExpireTime()
    {
        $this->keyExpireTime = self::getMilliTime() + $this->expire;
        return $this->keyExpireTime;
    }

    //获取当前的毫秒时间戳
    public static function getMilliTime()
    {
        return floor(microtime(true) * 1000);
    }
}